package MasterManagers;

import MasterManagers.SocketManager.SocketThread;
import com.google.common.collect.Table;

import java.io.IOException;
import java.util.*;

/**
 * 记录所有连结果的ip
 * 记录当前活跃的ip，以及ip对应的 table list
 * */

public class TableManager {
    private Map<String, List<String>> TableInfo;  // table -> ip1 ip2
    private List<String> serverList;        // region
    private Map<String, List<String>> liveServer; // Serverip -> table list
    private Map<String, List<SocketThread>> socketThreadMap;  // ip -> socket，只保存region
//    private Map<String, SocketThread> clientThreadMap;  //只保存client
    private List<String> clientList;

    public TableManager() throws IOException {
        TableInfo = new HashMap<>();
        serverList = new ArrayList<>();
        liveServer = new HashMap<>();
        socketThreadMap = new HashMap<>();
        clientList = new ArrayList<>();
    }
    public List<String> getClient(){
        return clientList;
    }

    public void addClient(String hostURL){
        if(!clientList.contains(hostURL)){
            clientList.add(hostURL);
        }
    }

    public List<String> getServerList(){return serverList;}


    // ---------- 有关 server 的------------

    /**
     * 找到当前最适合的Server 标准就是server对应的table list最少
     * @return
     */
    public List<String> getIdealServer(){
        if(liveServer.isEmpty()) return  null;
        List<String> result = new ArrayList<>();
        int min = Integer.MAX_VALUE;
        String ip = "";
        for(Map.Entry<String, List<String>> entry : liveServer.entrySet()){
            if(entry.getValue().size() < min){
                min = entry.getValue().size();
                ip = entry.getKey();
            }
        }
        result.add(ip);
        // 只有一个region的情况，返回的主/副region的ip一样
        if(serverNum() == 1){
            return result;
        }
        if(serverNum() == 2){
            for(Map.Entry<String, List<String>> entry : liveServer.entrySet()){
                if(!Objects.equals(entry.getKey(), result.get(0))){
                    result.add(entry.getKey());
                    return result;
                }
            }
        }
        // 副 region
        int min1 = Integer.MAX_VALUE;
        int min2 = Integer.MAX_VALUE;
        String ip1 = "";
        String ip2 = "";
        for(Map.Entry<String, List<String>> entry : liveServer.entrySet()){
            int number = entry.getValue().size();
            if(number < min1 && !Objects.equals(entry.getKey(), result.get(0))){
                if(number < min2){
                    ip1 = ip2;
                    min1 = min2;
                    min2 = number;
                    ip2 = entry.getKey();
                }
                else{
                    ip1 = entry.getKey();
                    min1 = number;
                }
            }
        }
        result.add(ip2);
        result.add(ip1);
        return result;
    }

    /**
     *
     * @param hostURL
     * @return 合适的region ip作为接替
     * 这里只需要返回一个就行，作为迁移的Region
     */
    public String getIdealServer(String hostURL, String table){
        Integer min = Integer.MAX_VALUE;
        String result = "";
        List<String> regions = TableInfo.get(table);
        for(Map.Entry<String, List<String>> entry : liveServer.entrySet()){
            if(!(regions.contains(entry.getKey())) && entry.getValue().size()<min){
                min = entry.getValue().size();
                result = entry.getKey();
            }
        }
        return result;
    }

    public boolean hasServer(String hostURL){
        boolean result = false;
        for(String s: serverList){
            if(s.equals(hostURL))
                result = true;
        }
        return result;
    }

    public boolean inLiveServer(String hostURL) {
        for (String key : liveServer.keySet()) {
            if (hostURL.equals(liveServer.get(key)))
                return true;
        }
        return false;
    }

    public void addServer(String hostURL){
        if(!hasServer(hostURL)) {
            serverList.add(hostURL);
            // 不仅仅要加上这个主机，还要为其添加一个列表 用来记录所有的table
            List<String> empty_list = new ArrayList<>();
            liveServer.put(hostURL, empty_list);
        }
        System.out.println("Add region successfully");
    }

//    public void deleteServer(String hostURL){
//        liveServer.remove(hostURL);
//    }
    public void deleteServer(String hostURL){
        if(!serverList.contains(hostURL))
            return;
        List<String> tables = liveServer.get(hostURL);
        liveServer.remove(hostURL);
        serverList.remove(hostURL);
        socketThreadMap.remove(hostURL);
        for(String table : tables){
            List<String> ips = TableInfo.get(table);
            ips.remove(hostURL);
            TableInfo.put(table, ips);
        }
    }

    public void deleteClient(String hostURL){
        if(!clientList.contains(hostURL))
            return;
        clientList.remove(hostURL);
        socketThreadMap.remove(hostURL);
    }

    public int serverNum(){
        return liveServer.size();
    }

    public String getUniqueServer(){
        String hostURL = "";
        for(Map.Entry<String, List<String>> entry : liveServer.entrySet())
            hostURL = entry.getKey();
        return hostURL;
    }

    // 将table所属的三个region的ip:port返回
    public List<String> getInetAddress(String table){
        System.out.println("Get table " + table + "'s IPs");
        if(TableInfo.containsKey(table)){
            return TableInfo.get(table);
        }
        return null;
    }

    public void recoverServer(String hostUrl) {
        List<String> temp = new ArrayList<>();
        liveServer.put(hostUrl,temp);
    }

    // ---------- 有关 table 的------------

    public void addTable(String table, String ip){
        System.out.println("Add table: ");
        System.out.println(table + "," + ip);
        if(TableInfo.containsKey(table)){
            List<String> ips = TableInfo.get(table);
            ips.add(ip);
        }
        else{
            List<String> ips = new ArrayList<>();
            ips.add(ip);
            TableInfo.put(table, ips);
        }
        // 如果当前的ip已存在活跃列表中，那么将table计入其对应的list里保存。
        if(liveServer.containsKey(ip))
            liveServer.get(ip).add(table);
        else{ // 先新建一个空的列表，然后将这一新的ip<->list对存入
            List<String> empty_list = new ArrayList<>();
            empty_list.add(table);
            liveServer.put(ip, empty_list);
        }
    }

    // TODO
//    public void deleteTable(String table, String ip){
//        TableInfo.remove(table);
//        liveServer.get(ip).removeIf(table::equals);
//    }

    public void deleteTable(String table){
        List<String> ips = TableInfo.get(table);
        TableInfo.remove(table);
        for(String ip0 : ips){
            liveServer.get(ip0).removeIf(table::equals);
        }
    }

    /***
     * 当系统内只有一个region时候，需要把所有表TableInfo中原来没挂的那张region的ip给抹去。
     * 现在 主 副 region机制失效，变成只有主
     */
    public void exchangeTable(String region){
        for(Map.Entry<String, List<String>> entry : TableInfo.entrySet()){
            if (entry.getValue().get(0).equals(region))
                entry.getValue().set(1, region);
            else if (entry.getValue().get(1).equals(region))
                entry.getValue().set(0, region);
            else if(entry.getValue().size() > 2 && entry.getValue().get(2).equals(region)){
                entry.getValue().set(0, region);
            }
        }
    }

    /***
     *
     * @param newRegion
     * @param oldRegion
     * @param change_Table
     */
    public void exchangeTable(String newRegion, String oldRegion, String change_Table) {
        List <String> tableList = getTableList(oldRegion);
        // oldRegion 的 TableList 下的表格，要全部更新tableInfo

        List<String> ips = TableInfo.get(change_Table);
        ips.remove(oldRegion);
        ips.add(newRegion);
        TableInfo.put(change_Table,ips);
        System.out.println("after change ips = " + TableInfo.get(change_Table).get(0) + " " +  TableInfo.get(change_Table).get(1));
        // 给新的Region 更改 liveServer.TableList
        List <String> bestInetTable = liveServer.get(newRegion);
        bestInetTable.add(change_Table);
        liveServer.put(newRegion,bestInetTable);
        List <String> oldRegion_tables = liveServer.get(oldRegion);
        oldRegion_tables.remove(change_Table);
        liveServer.put(oldRegion,oldRegion_tables);
    }

    public List<String> getTableList(String hostUrl) {
        for(Map.Entry<String, List<String>> entry : liveServer.entrySet()){
            if(entry.getKey().equals(hostUrl)){
                return  entry.getValue();
            }
        }
        return null;
    }

    public String showTables(){
        String tables = "";
        for(Map.Entry<String, List<String>> entry : TableInfo.entrySet()){
            tables += entry.getKey() + ",";
        }
        return tables;
    }

    public String getRegion1(String hostUrl, String tableName){
        List<String> regions = TableInfo.get(tableName);
        if (regions.get(0).equals(hostUrl))
            return regions.get(1);
        else
            return regions.get(0);
    }

    //--------------------Socket---------------------
    public void addSocketThread(String hostUrl, SocketThread socketThread) {
        List<SocketThread> origin = socketThreadMap.getOrDefault(hostUrl,new ArrayList<>());
        origin.add(socketThread);
        socketThreadMap.put(hostUrl,origin);
    }
    public List<SocketThread> getSocketThread(String hostUrl) {
        for(Map.Entry<String, List<SocketThread>> entry : socketThreadMap.entrySet()){
            if(entry.getKey().equals(hostUrl))
                return entry.getValue();
        }
        return null;
    }
//    public void addClientThread(String hostUrl, SocketThread socketThread){
//        clientThreadMap.put(hostUrl,socketThread);
//    }
//    public SocketThread getClientThread(String hostUrl){
//        for(Map.Entry<String,SocketThread> entry : clientThreadMap.entrySet()){
//            if(entry.getKey().equals(hostUrl)){
//                return entry.getValue();
//            }
//        }
//        return null;
//    }

}
